/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package filetrans;

import DAO.ServerDAO;
import DAO.TorrentDAO;
import View.Panel;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.j3d.View;
import javax.swing.JOptionPane;
import model.ServerInfo;
import model.Torrent;
import xmlreader.CommandInterpreter;
import xmlwriter.CommandBuilder;

/**
 *
 * @author Oscar
 */
public class Client {

    private Socket socket;
    private Boolean Autenticado = false;
    private String IP;
    private int IntResponse;
    private String StringResponse;
    private ServerDAO serverDao;
    private TorrentDAO torrentDao;
    private Torrent torrent;
    private DataOutputStream output;

    private String userNickname;

    public Client()  {

        this.serverDao = ServerDAO.getInstance();
        this.torrentDao = TorrentDAO.getInstance();

        IntResponse = 0;
        StringResponse = null;
        
    }

    /**
     *
     */
    public void connect() throws IOException, Exception {
        try {
            this.IP = getIpWithLessLoad();

            //
            // Create a connection to the server socket on the server application
            //

            if (validateIP(IP)) {
                byte[] b = new byte[4];
                b[0] = (byte) (Integer.parseInt(IP.substring(0, IP.indexOf("."))));
                IP = IP.substring(IP.indexOf(".") + 1, IP.length());
                b[1] = (byte) (Integer.parseInt(IP.substring(0, IP.indexOf("."))));
                IP = IP.substring(IP.indexOf(".") + 1, IP.length());
                b[2] = (byte) (Integer.parseInt(IP.substring(0, IP.indexOf("."))));
                IP = IP.substring(IP.indexOf(".") + 1, IP.length());
                b[3] = (byte) (Integer.parseInt(IP));



                InetAddress host = InetAddress.getByAddress(b);
                String ip = host.getHostAddress();

                if (host.isReachable(2500)) {
                    this.socket = new Socket(host, 7777);
                } else {
                    throw new Exception("Host Inalcanzable.");
                }
            } else {
                throw new Exception("IP Invalida.");
            }


        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw new Exception("Error en la comunicacion.");
        } catch (NumberFormatException e) {
            throw new Exception("Campo de IP vacio.");
        }
    }


    public void connectTo(String myIp) throws IOException, Exception {
        try {
            this.IP = myIp;

            //
            // Create a connection to the server socket on the server application
            //

            if (validateIP(IP)) {
                byte[] b = new byte[4];
                b[0] = (byte) (Integer.parseInt(IP.substring(0, IP.indexOf("."))));
                IP = IP.substring(IP.indexOf(".") + 1, IP.length());
                b[1] = (byte) (Integer.parseInt(IP.substring(0, IP.indexOf("."))));
                IP = IP.substring(IP.indexOf(".") + 1, IP.length());
                b[2] = (byte) (Integer.parseInt(IP.substring(0, IP.indexOf("."))));
                IP = IP.substring(IP.indexOf(".") + 1, IP.length());
                b[3] = (byte) (Integer.parseInt(IP));



                InetAddress host = InetAddress.getByAddress(b);
                String ip = host.getHostAddress();

                if (host.isReachable(2500)) {
                    this.socket = new Socket(host, 7777);
                } else {
                    throw new Exception("Host Inalcanzable.");
                }
            } else {
                throw new Exception("IP Invalida.");
            }


        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw new Exception("Error en la comunicacion.");
        } catch (NumberFormatException e) {
            throw new Exception("Campo de IP vacio.");
        }
    }

    /**
     * Valida una IP
     *
     * @param IP la IP a validar
     * @return true si es correcta, de lo contrario false
     */
    public static boolean validateIP(String IP) {
        int value = 0;

        for (int i = 0; i < 4; i++) {
            if (i == 3) {
                value = Integer.parseInt(IP);
            } else {
                value = Integer.parseInt(IP.substring(0, IP.indexOf(".")));
                IP = IP.substring(IP.indexOf(".") + 1, IP.length());
            }
            if ((value < 0) || (value > 255)) {
                return false;
            }

        }

        return true;
    }

    public String getIpWithLessLoad() {
        
        int load = 1000000000;
        int minload = 1000000000;
        String minIp = null;

        for (ServerInfo server : this.serverDao.Servers) {

            try {

                load = getLoad(server.getIp());
                
            } catch (UnknownHostException ex) {
                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            } catch (Exception ex) {
                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            }

            if (load < minload) {

                minload = load;

                minIp = server.getIp();
            }

        }


        return minIp;
    }

    /**
     * Autentica un usuario
     *
     * @param user Nombre de usuario
     * 
     * @param pass Password del usuario
     *
     * @return True si es correcta la informacion, de lo contrario false
     *
     * @throws ClassNotFoundException
     * @throws UnknownHostException
     * @throws Exception
     */
    public boolean Authenticate(String user, String pass) throws ClassNotFoundException, UnknownHostException, Exception {

        

        try {
            connect();
            
            //Se crea un mensaje de autenticacion
            CommandBuilder message = new CommandBuilder(socket);

            //se envia el mensaje al servidor
            message.BuildAuthenticateMessage(user, pass)
                   .flush();

            CommandInterpreter interpreter = new CommandInterpreter();
            interpreter.readCommand(socket.getInputStream(), this);

            if (StringResponse.equalsIgnoreCase("Yes")) {

                Autenticado = true;
                return true;
            }

            Autenticado = false;
            return false;

        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw new Exception("Problema para acceder, usuario invalido.");
        } catch (Exception e) {
            throw new Exception("Problema para acceder, usuario invalido.");
        }




    }

    /**
     * registra un cliente
     *
     * @param user Nombre de usuario
     *
     * @param pass Password del usuario
     *
     * @return True si es correcta la informacion, de lo contrario false
     *
     * @throws ClassNotFoundException
     * @throws UnknownHostException
     * @throws Exception
     */
    public boolean Register(String user, String pass) throws ClassNotFoundException, UnknownHostException, Exception {

        

        try {
            
            connect();

            //Se crea un mensaje de registro
            CommandBuilder message = new CommandBuilder(socket);

            //se envia el mensaje al servidor
            message.BuildRegisterMessage(user, pass)
                   .flush();

            CommandInterpreter interpreter = new CommandInterpreter();
            interpreter.readCommand(socket.getInputStream(), this);

            if (StringResponse.equalsIgnoreCase("Yes")) {

                return true;
            }
            
            return false;

        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw new Exception("Problema para acceder.");
        } catch (Exception e) {
            throw new Exception("Problema para acceder.");
        }




    }

    /**
     * Retorna la carga de un servidor
     *
     * @return Valor de la carga del servidor
     *
     * @throws ClassNotFoundException
     * @throws UnknownHostException
     * @throws Exception
     */
    public int getLoad(String ip) throws ClassNotFoundException, UnknownHostException, Exception {

        

        try {

            connectTo(ip);

            //Se crea un mensaje de registro
            CommandBuilder message = new CommandBuilder(socket);

            //se envia el mensaje al servidor
            message.BuildGetLoadMessage().flush();

            CommandInterpreter interpreter = new CommandInterpreter();
            interpreter.readCommand(socket.getInputStream(), this);

            return Integer.parseInt(StringResponse);

        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw new Exception("Problema para acceder.");
        } catch (Exception e) {
            throw new Exception("Problema para acceder.");
        }

    }



    public void download(String nameFile, Thread thread, Panel panel) throws Exception {

        

        try {

            connect();

            //Se crea un mensaje de registro
            CommandBuilder command = new CommandBuilder(socket);






            Torrent myTorrent = torrentDao.exist(userNickname, nameFile);

            //se envia el mensaje al servidor
            
            command.BuildDownloadMessage(this.userNickname,
                    nameFile, myTorrent.getBlock()).flush();
            

             ObjectInputStream ois = new ObjectInputStream(
                    socket.getInputStream());

            long totalsize = (Long) ois.readObject();

            BufferedInputStream bis;
            BufferedOutputStream bos;
            BufferedOutputStream boss;

            byte[] receivedData;
            int in;
            Boolean flag = false;
            Boolean flag2 = false;
            try {

                output = new DataOutputStream(socket.getOutputStream());



                receivedData = new byte[8192];


                if (Integer.parseInt(myTorrent.getBlock()) > 0) {

                    flag = true;

                    boss = new BufferedOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/files/TEMP/TEMP"
                            + myTorrent.getName()));

                    bis = new BufferedInputStream(socket.getInputStream());

                    while ((in = bis.read(receivedData)) != -1) {
                        if (!socket.isBound() || !socket.isConnected() || socket.isClosed()) {
                           flag2=true;
                          // panel.checkArray(nameFile);
                          // panel.regenerate();
                            thread.interrupt();
                        } else {
                            boss.write(receivedData, 0, in);

                        }
                    }
                } else {

                    bos = new BufferedOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/files/FINAL/FINAL"
                            + myTorrent.getName()));

                    bis = new BufferedInputStream(socket.getInputStream());

                    Long num1 = new Long(totalsize);

                    while ((in = bis.read(receivedData)) != -1) {
                        Long num2  = new Long(Long.parseLong(String.valueOf(Integer.valueOf(myTorrent.getBlock()) * 8192)));

                        if (!socket.isBound() || !socket.isConnected() || socket.isClosed()) {
                            flag2=true;
                           //panel.checkArray(nameFile);
                          // panel.regenerate();
                            thread.interrupt();
                        } else {
                            bos.write(receivedData, 0, in);
                            this.torrentDao.addBlock(this.userNickname, nameFile);
                            this.torrentDao.commit();
                        }
                    }
                    bos.close();
                }
                if (flag) {
                    join(myTorrent.getName());
                } if(!flag2) {
                    rename(myTorrent.getName());
                }
                bis.close();

JOptionPane.showMessageDialog(null,
                    "Finalizo",
                    "Info", JOptionPane.INFORMATION_MESSAGE);

            } catch (IOException e) {
            }


        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw new Exception("Error en la comunicacion.");
        } catch (Exception e) {
            throw new Exception("File sobrepasa el limite permitido.");

        }
    }


    public void join(String name) {
        try {

            BufferedInputStream bis;
            BufferedOutputStream bos;
            byte[] receivedData;
            int in;
            receivedData = new byte[8192];
            bos = new BufferedOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/files/"
                    + name));
            bis = new BufferedInputStream(new FileInputStream(System.getProperty("user.dir") + "/files/FINAL/FINAL" + name));

            while ((in = bis.read(receivedData)) != -1) {
                bos.write(receivedData, 0, in);
            }
            bis = new BufferedInputStream(new FileInputStream(System.getProperty("user.dir") + "/files/TEMP/TEMP" + name));

            while ((in = bis.read(receivedData)) != -1) {
                bos.write(receivedData, 0, in);
            }

            delete(System.getProperty("user.dir") + "/files/FINAL/FINAL" + name);
            delete(System.getProperty("user.dir") + "/files/TEMP/TEMP" + name);

        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void delete(String name) {
        File file = new File(name);

        // Make sure the file or directory exists and isn't write protected
        if (file.exists()) {
            file.delete();
        }
    }

    public void rename(String name) {

        File file = new File(System.getProperty("user.dir") + "/files/FINAL/FINAL" + name);

        file.renameTo(new File(System.getProperty("user.dir") + "/files/" + name));
    }


    public String[] remoteDir() throws ClassNotFoundException, UnknownHostException, Exception {

        

        String[] files = null;
        int quantity = 0;

        try {
            
            connect();

            //Se crea un mensaje de registro
            CommandBuilder sender = new CommandBuilder(socket);

            //se envia el mensaje al servidor
            sender.BuildGetFilesMessage().flush();

            ObjectInputStream ois = new ObjectInputStream(
                    socket.getInputStream());
            String message = (String) ois.readObject();

            quantity = Integer.parseInt(message.substring(4,
                    message.indexOf("@end|begin@")));
            message = message.substring(message.indexOf("@end|begin@")
                    + ("@end|begin@").length(), message.length());

            if (quantity != 0) {
                files = new String[quantity];
            }

            for (int i = 0; i < quantity; i++) {
                files[i] = message.substring(0, message.indexOf("@end|begin@"));
                message = message.substring(message.indexOf("@end|begin@")
                        + ("@end|begin@").length(), message.length());
            }
            ois.close();

        } catch (ClassNotFoundException ex) {
            throw ex;
        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw new Exception("Problema para acceder al directorio.");
        } catch (Exception e) {
            throw new Exception("Problema para acceder al directorio.");
        }

        if (quantity == 0) {
            files = new String[1];
            files[0] = "Directory Empty";
        }
        return files;

    }






    /**
     * Verifica que un cliente este autenticado
     *
     * @return true si esta autenticado, de lo contrario false
     */
    public Boolean isAutenticado(String user) {
        
        if (Autenticado)
            this.userNickname = user;

        return Autenticado;
    }

    public String getUserNickname() {
        return userNickname;
    }

    public void setUserNickname(String userNickname) {
        this.userNickname = userNickname;
    }

    public Torrent getTorrent() {
        return torrent;
    }

    public void setTorrent(Torrent torrent) {
        this.torrent = torrent;
    }

    public Boolean setAutenticado(boolean a) {
        return Autenticado = a;
    }
    

    public int getIntResponse() {
        return IntResponse;
    }

    public void setIntResponse(int IntResponse) {
        this.IntResponse = IntResponse;
    }

    public String getStringResponse() {
        return StringResponse;
    }

    public void setStringResponse(String StringResponse) {
        this.StringResponse = StringResponse;
    }

    public ServerDAO getServerDao() {
        return this.serverDao;
    }

    public void setServerDao(ServerDAO serverDao) {
        this.serverDao = serverDao;
    }



}
